require 'set'

def let(symbol, what)
  what.call(symbol)
end

class Axis
  def initialize
    @positions = {}
    puts "Axis created"
  end
  
  def register_at(object, value)
    @positions[object] = value
  end
  
  def position_of(object)
    @positions[object]
  end
  
  def positions
    @positions
  end
end



class ClassDefinition
  
  def initialize(klass)
    @super_class = klass
    @properties = {}
    @uponable = {}
  end

  def with(arg)
    arg.each_pair do |prop, klass|
      @properties[prop] = klass
    end
    return self
  end

  def upon(klass, *args)
    # Typecheck that each args have an axis type in klass
    args.each do |prop|
      if klass.property_spec.key? prop
        unless klass.property_spec[prop] <= Axis
          raise TypeError, "Property : #{prop} in #{klass} is not an Axis"
        end
      else
        raise TypeError, "No such property : #{prop} in #{klass}"
      end
    end
    
    @uponable[klass] ||= Set.new
    @uponable[klass].merge(args)
    return self
  end
  
  def call(symbol)
    Object.class_eval "class #{symbol} < #{@super_class} \nend"
    klass = eval "#{symbol}"
    
    @properties.each_pair do |propname, type|
      klass.class_eval "def #{propname}
          @#{propname} ||= #{type}.new
        end"
      klass.class_eval "def #{propname}=(value)
          unless value.kind_of?(#{type})
            raise TypeError, '#{propname} must be a #{type}'
          else
            @#{propname}=value
          end
        end"
    end
    
    klass.location_spec = @uponable
    klass.property_spec = @properties
  end
end


class Entity
  
  def initialize(*dict)
    dict[0].each_pair do |prop, value|
      self.send "#{prop}=", value
    end if dict.size > 0
  end

  def locate(where, dict)
    l_spec = self.class.location_spec
    if l_spec.key?(where.class) then
      keys_set = Set.new(dict.keys)
      valid_set = l_spec[where.class]
      if keys_set.subset?(valid_set) then
        dict.each_pair do |axis_name, value|
          axis = where.send "#{axis_name}"
          axis.register_at(self, value)
        end
      else
        fail_set = keys_set - valid_set
        raise TypeError, "#{self.class} is not upon #{where.class} through [#{fail_set.to_a.join(",")}]"
      end
    else
      raise TypeError, "#{self.class} is not upon #{where.class}"
    end
  end

  def position(where)
    results = {}
    l_spec = self.class.location_spec
    if l_spec.key?(where.class) then
      l_spec[where.class].each do |prop|
        results[prop] = where.send(prop).position_of(self)
      end
    else
      raise TypeError, "#{self.class} is not upon #{where.class}"
    end
    results
  end
  
  class << self
    def location_spec
      @location_spec || {}
    end
    
    def location_spec=(d)
      @location_spec = (self == Entity) ? d : superclass.location_spec.merge(d)
    end
    
    def property_spec
      @property_spec || {}
    end
    
    def property_spec=(d)
      @property_spec = (self == Entity) ? d : superclass.property_spec.merge(d)
    end
    
    def create_class(symbol)
      ClassDefinition.new(self).create_class(symbol)
    end

    def with(arg)
      ClassDefinition.new(self).with(arg)
    end

    def upon(klass, *args)
      ClassDefinition.new(self).upon(klass, args)
    end
  end
end


class Event ; end

class EventEmitter

  def emit(event)
    
  end
  
end



class UnitCounterLevel  
  @currentCountStep
  # eventually empty
  @countStepList

  @eventClass


  def prev_value
    if @countStepList.size > 0 then
      @countStepList.before(@currentCountStep)
    else
      @currentCountStep.prev_value
    end
  end

  def value
    if @countStepList.size > 0 then
      @currentCountStep
    else
      @currentCountStep.value
    end
  end
  

  def check_change(unit_counter, prev_value, value)
    change = @currentCountStep.send :changed, unit_counter, prev_value, value

    if change then
      if @countStepList.size > 0 then
        @currentCountStep = @countStepList.after(@currentCountStep)
      else
        @currentCountStep.increment
      end   
    end
    
    change
  end
  
end

class UnitCounter < EventEmitter

  @levels = []

  def initialize
    
  end

  def BaseValue
    @baseValue
  end

  def BaseValue=(v)
    prev_val = @baseValue
    @baseValue = v
    check_level_change(0, prev_val, @baseValue)
  end

  def check_level_change(index, prevVal, value)
    if index < @levels.size then
      if @levels[index].check_change(self, prevVal, value) then
        emit @levels[index].eventClass.new
        check_level_change(index + 1, @levels[index].prev_value, @levels[index].value)
      end
    end
  end
  
  class << self
    def withLevels(countstep_dict)
      Proc.new do |symbol|
        Object.class_eval "class #{symbol} < #{self.to_s} \nend"
        klass = eval "#{symbol}"
      end
    end
  end
end
